from xml.etree import cElementTree as etree
from loadstone import db,geocalc
from db import mult
from crc16 import crc16
import ukrtools
import time
import logging
import tempfile
import os
import sqlite3

def N_(text):
	return text

amenityLabels={
	"aerodrome":N_("aerodrome"),
	"arts_centre":N_("arts centre"),
	"atm":N_("atm"),
	"attraction":N_("attraction"),
	"bakery":N_("bakery"),
	"bank":N_("bank"),
	"basketball":N_("basketball"),
	"bench":N_("bench"),
	"beverages":N_("beverages"),
	"bicycle_rental":N_("bicycle rental"),
	"books":N_("books"),
	"bureau_de_change":N_("bureau de change"),
	"bus_station":N_("bus station"),
	"bus_stop":N_("bus_stop"),
	"cafe":N_("cafe"),
	"car":N_("car"),
	"car_repair":N_("car repair"),
	"cemetery":N_("cemetery"),
	"cinema":N_("cinema"),
	"circus":N_("circus"),
	"cityhall":N_("cityhall"),
	"city":N_("city"),
	"clothes":N_("clothes"),
	"college":N_("college"),
	"computer":N_("computer"),
	"convenience":N_("convenience"),
	"crematorium":N_("crematorium"),
	"courthouse":N_("courthouse"),
	"dentist":N_("dentist"),
	"doityourself":N_("doityourself"),
	"doctors":N_("doctors"),
	"electronics":N_("electronics"),
	"embassy":N_("embassy"),
	"fast_food":N_("fast food"),
	"fire_station":N_("fire station"),
	"florist":N_("florist"),
	"forest":N_("forest"),
	"fountain":N_("fountain"),
	"football":N_("football"),
	"fuel":N_("fuel"),
	"furniture":N_("furniture"),
	"garden":N_("garden"),
	"hairdresser":N_("hairdresser"),
	"halt":N_("halt"),
	"hamlet":N_("hamlet"),
	"hockey":N_("hockey"),
	"hospital":N_("hospital"),
	"hotel":N_("hotel"),
	"ice_rink":N_("ice-rink"),
	"industrial":N_("industrial"),
	"island":N_("island"),
	"kindergarten":N_("kindergarten"),
	"kiosk":N_("kiosk"),
	"laundry":N_("laundry"),
	"library":N_("library"),
	"mall":N_("mall"),
	"memorial":N_("memorial"),
	"monument":N_("monument"),
	"motel":N_("motel"),
	"motor":N_("motor"),
	"museum":N_("museum"),
	"nightclub":N_("nightclub"),
	"optician":N_("optician"),
	"outdoor":N_("outdoor"),
	"parking":N_("parking"),
	"park":N_("park"),
	"pharmacy":N_("pharmacy"),
	"pier":N_("pier"),
	"place_of_worship":N_("place of worship"),
	"playground":N_("playground"),
	"police":N_("police"),
	"post_office":N_("post office"),
	"prison":N_("prison"),
	"pub":N_("pub"),
	"public_building":N_("public building"),
	"restaurant":N_("restaurant"),
	"school":N_("school"),
	"shoes":N_("shoes"),
	"shopping_center":N_("shopping center"),
	"skating":N_("skating"),
	"skiing":N_("skiing"),
	"soccer":N_("soccer"),
	"sports_centre":N_("sports centre"),
	"stadium":N_("stadium"),
	"station":N_("station"),
	"stationery":N_("stationery"),
	"studio":N_("studio"),
	"suburb":N_("suburb"),
	"subway_entrance":N_("subway entrance"),
	"supermarket":N_("supermarket"),
	"swimming":N_("swimming"),
	"telephone":N_("telephone"),
	"telescope":N_("telescope"),
	"tennis":N_("tennis"),
	"theatre":N_("theatre"),
	"toilets":N_("toilets"),
	"water_tower":N_("water tower"),
	"tower":N_("tower"),
	"townhall":N_("townhall"),
	"town":N_("town"),
	"tram_stop":N_("tram stop"),
	"university":N_("university"),
	"vending_machine":N_("vending machine"),
	"veterinary":N_("veterinary"),
	"village":N_("village"),
	"waste_disposal":N_("waste disposal"),
	"level_crossing":N_("level crossing"),
	"crossing":N_("crossing"),
	"zoo":N_("zoo"),
}

def translateAmenity(amenity):
	if amenity in amenityLabels: 
		return _(amenityLabels[amenity])
	else:
		return amenity

class BaseOSMConverter(object):
	amenity_list=("amenity","shop","railway","tourism","historic","place","aeroway","man_made","sport","leisure")
	name_list=["name","description","operator"]

	def __init__(self,source,email,preferredNames=None,includePOIs=True,transliterateUkrainian=False,intersectionDelimiter=" X "):
		self.transliterateUkrainian=transliterateUkrainian
		self.intersectionDelimiter=intersectionDelimiter
		if preferredNames is not None: self.name_list=preferredNames+self.name_list
		self.user_id=crc16(email)
		self.includePOIs=includePOIs
		self.db=db.DB()
		self.log=logging.getLogger("OSMConverter")
		self.id = int(time.time())-315360000
		self.work(source)

	def work(self,source):
		raise NotImplementedError

	def preprocessName(self,name):
		if self.transliterateUkrainian and ukrtools.strContainsUkrainian(name):
			name=ukrtools.transliterateUkrToRus(name)
		return name.replace('"',"'").replace("\n"," ").replace("\r"," ")

	def addPOI(self,name,latitude,longitude):
		self.db.point.append(db.PointRecord(id=self.id,userid=self.user_id,name=name,latitude=int(latitude*mult),longitude=int(longitude*mult),accuracy=1,satellites=10,priority=0))
		self.id+=1

	def constructName(self,tags):
		name=amenity=highway=building=street=address=None
		names={}
		for k,v in tags.iteritems():
			if k in self.name_list: names[k]=v
			elif k=="highway" and v=="bus_stop":
				amenity=v
			elif k in self.amenity_list: amenity=v
			elif k=="tactile_paving" and v=="yes":
				name=_("guideline: %s")%name
			elif k=="highway": highway=v
			elif k=="building": building=v
			elif k in ('addr:street', 'street:name'): street=v
			elif k in ('addr:housenumber','addr:housename', 'street:nr'): address=v
		if amenity=="yes": 
			amenity=None
		for s in self.name_list:
			if s in names:
				name=names[s]
				break
		if building and (address or street):
			street="" if street is None else street + " "
			if address is None: address=""
			name=street + address
		if name:
			name=self.preprocessName(name)
			if amenity:
				name="%s: %s"%(translateAmenity(amenity),name)
		elif amenity and amenity not in ('common','pitch','park'): name=translateAmenity(amenity)
		return name,amenity,building,highway

	def _constructIntersection(self,s1,s2):
		return s1+self.intersectionDelimiter+s2

class EtreeConverter(BaseOSMConverter):
	def nameFromElement(self,item):
		tags={}
		for c in item.getiterator("tag"):
			k=c.get("k")
			v=c.get("v")
			tags[k]=v
		return self.constructName(tags)

class InmemoryConverter(EtreeConverter):
	def work(self,source):
		self.log.debug(_("Parsing XML"))
		tree=etree.parse(source)
		del source
		self.log.debug(_("Extracting nodes"))
		nodes=self.extractNodes(tree)
		self.log.debug(_("Processing ways"))
		ways=[]
		for way in tree.getiterator("way"):
			refs=[]
			for c in way.getiterator("nd"):
				refs.append(int(c.get('ref')))
			###
			name,amenity,building,highway=self.nameFromElement(way)
			if not name or name in ('FIXME','(type road name)'):
				continue
			if (building or amenity) and self.includePOIs:
				ref=refs.pop()
				if len(refs)>0 and refs[0]==ref:
					lat=0.0
					lon=0.0
					for id in refs:
						node=nodes[id]
						lat+=node[1]
						lon+=node[2]
					count=len(refs)
					lat/=count
					lon/=count
					node=nodes[id]
				else:
					node=nodes[ref]
					lat=node[1]
					lon=node[2]
				self.addPOI(name,lat,lon)
			elif highway:
				for ref in refs:
					node=nodes[ref]
					ways.append((name,node[1],node[2]))
			way.clear()
		del nodes
		self.log.debug(_("Calculating intersections"))
		self.calculateIntersections(ways)

	def extractNodes(self,tree):
		nodes={}
		for node in tree.getiterator("node"):
			name=self.nameFromElement(node)[0]
			if name and self.includePOIs:
				self.addPOI(name,float(node.get('lat')),float(node.get('lon')))
			nodes[int(node.get("id"))]=(name,float(node.get("lat")),float(node.get("lon")))
			node.clear()
		return nodes

	def calculateIntersections(self,ways):
		ways.sort(key=lambda k:(k[1],k[2]))
		prevName=None
		prevLat=prevLon=0.0
		map1=[]
		map2=[]
		for way in ways:
			distance=geocalc.EllipsoidDistance(way[1],way[2],prevLat,prevLon)
			if distance>0.001:
				map1.append((way[0],way[1],way[2],False))
			else: #distance < 0.001
				if not way[0]==prevName:
					map1.append((self._constructIntersection(way[0],prevName),way[1],way[2],True))
			prevName,prevLat,prevLon=way
		#ORIGINAL COMMENT: Deal with leftovers from intersections.
		map1.sort(key=lambda k:(k[1],k[2],not k[3]))
		prevLat=prevLon=0.0
		for r in map1:
			distance=geocalc.EllipsoidDistance(r[1],r[2],prevLat,prevLon)
			if distance<=0.001: continue
			map2.append(r)
			prevLat,prevLon=r[1],r[2]
		del map1
		map2.sort(key = lambda k:(k[0],k[1],k[2]))
		prevName=None
		prevLat=prevLon=0.0
		for r in map2:
			distance=geocalc.EllipsoidDistance(r[1],r[2],prevLat,prevLon)
			if distance<=0.025 and r[0]==prevName: continue
			if r[3]: self.addPOI(r[0],r[1],r[2])

class Sqlite3UsingConverter(EtreeConverter):
	initialMixture=(
		'create table nodes (id INTEGER PRIMARY KEY, name TEXT, lat REAL, lon REAL)',
		'create table ways (name TEXT, lat REAL, lon REAL)',
		'create table map1 (name TEXT, lat REAL, lon REAL, cross bool)',
		'create table map2 (name TEXT, lat REAL, lon REAL, cross bool)',
	)

	def __init__(self,*args,**kwargs):
		with tempfile.NamedTemporaryFile(delete=False) as f:
			self.sqliteDBName=f.name
		self.conn=sqlite3.connect(self.sqliteDBName)
		self.cursor=self.conn.cursor()
		for mixture in self.initialMixture:
			self.cursor.execute(mixture)
		self.conn.commit()
		super(Sqlite3UsingConverter,self).__init__(*args, **kwargs)

	def work(self,source):
		self.log.debug(_("Parsing XML"))
		# get an iterable
		context = etree.iterparse(source, events=("start", "end"))
		# turn it into an iterator
		context = iter(context)
		# get the root element
		event, root = context.next()
		for event,element in context:
			if event=="start": continue
			if element.tag=="node":
				name=self.nameFromElement(element)[0]
				if name and self.includePOIs:
					self.addPOI(name,float(element.get('lat')),float(element.get('lon')))
				self.cursor.execute('insert into nodes values (?, ?, ?,?)',(int(element.get("id")),name,float(element.get("lat")),float(element.get("lon"))))
			elif element.tag == "way":
				refs=[]
				for c in element.getiterator("nd"):
					refs.append(int(c.get('ref')))
				name,amenity,building,highway=self.nameFromElement(element)
				if not name or name in ('FIXME','(type road name)'):
					continue
				if (building or amenity) and self.includePOIs:
					ref=refs.pop()
					if len(refs)>0 and refs[0]==ref:
						lat=0.0
						lon=0.0
						for id in refs:
							node=self.cursor.execute("select * from nodes where id=?",(id,)).fetchone()
							lat+=node[2]
							lon+=node[3]
						count=len(refs)
						lat/=count
						lon/=count
						node=self.cursor.execute("select * from nodes where id=?",(id,)).fetchone()
					else:
						node=self.cursor.execute("select * from nodes where id=?",(ref,)).fetchone()
						lat=node[2]
						lon=node[3]
					self.addPOI(name,lat,lon)
				elif highway:
					for ref in refs:
						node=self.cursor.execute("select * from nodes where id=?",(ref,)).fetchone()
						if not node: 
							self.log.warning(_("Node %d is missing")%ref)
						else:
							self.cursor.execute("insert into ways values (?,?,?)",(name,node[2],node[3]))
			if element.tag not in ("nd","tag"): root.clear()
		self.cursor.execute('drop table nodes')
		self.conn.commit()
		source.close()
		self.log.debug(_("Calculating intersections"))
		self.calculateIntersections()

	def __del__(self):
		self.conn.close()
		os.unlink(self.sqliteDBName)

	def calculateIntersections(self):
		self.cursor.execute('select * from ways order by lat,lon')
		prevName=None
		prevLat=prevLon=0.0
		cur=self.conn.cursor()
		for way in self.cursor:
			distance=geocalc.EllipsoidDistance(way[1],way[2],prevLat,prevLon)
			if distance>0.001:
				cur.execute("insert into map1 values (?,?,?,?)",(way[0],way[1],way[2],False))
			else: #distance < 0.001
				if not way[0]==prevName:
					cur.execute("insert into map1 values (?,?,?,?)",(self._constructIntersection(way[0],prevName),way[1],way[2],True))
			prevName,prevLat,prevLon=way
		self.cursor.execute("drop table ways")
		self.conn.commit()
		#ORIGINAL COMMENT: Deal with leftovers from intersections.
		self.cursor.execute('select * from map1 order by lat,lon, cross desc')
		prevLat=prevLon=0.0
		for r in self.cursor:
			distance=geocalc.EllipsoidDistance(r[1],r[2],prevLat,prevLon)
			if distance<=0.001: continue
			cur.execute("insert into map2 values (?,?,?,?)",r)
			prevLat,prevLon=r[1],r[2]
		self.cursor.execute("drop table map1")
		self.conn.commit()
		self.cursor.execute('select * from map2 ORDER BY name, lat, lon')
		prevName=None
		prevLat=prevLon=0.0
		for r in self.cursor:
			distance=geocalc.EllipsoidDistance(r[1],r[2],prevLat,prevLon)
			if distance<=0.025 and r[0]==prevName: continue
			if r[3]: self.addPOI(r[0],r[1],r[2])
